home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / More Source / Pascal / Book Demos in Pascal / TicTacToe / TicTacToe.p < prev    next >
Text File  |  1995-05-26  |  14KB  |  534 lines

  1. program TicTacToe;
  2.  
  3. (* TicTacToe – prototype game example for the Mac Game book*)
  4. (* By Ingemar Ragnemalm 1995*)
  5.  
  6.     uses
  7. {$IFC UNDEFINED THINK_PASCAL}
  8.         Types, QuickDraw, Fonts, Events, Packages, Menus, Dialogs, Windows,{}
  9.         OSUtils, ToolUtils, OSEvents, Resources,
  10. {$ENDC}
  11.         Sound;
  12.  
  13.     type
  14.         SndListHandle = Handle;
  15.  
  16.     const
  17. (*Size of the array*)
  18.         kArraySizeH = 3;
  19.         kArraySizeV = 3;
  20.  
  21. (*Size of the tiles*)
  22.         kTileSizeH = 64;
  23.         kTileSizeV = 64;
  24.  
  25.         kNumTiles = 9;
  26.  
  27. (*Search depth - higher gives a better computer player*)
  28. (*Everything above 0 plays ok*)
  29.         kMaxDepth = 2;
  30.  
  31. (* All the possible states of a tile *)
  32.     type
  33.         TileState = (empty, red, green);
  34.  
  35. (* The window pointer *)
  36.     var
  37.         myWindow: WindowPtr;
  38.  
  39. (* Data structure describing the game board *)
  40.  
  41.     type
  42.         GameState = record
  43.                 tiles: array[0..kNumTiles] of TileState;    (* What is in each tile? *)
  44.                 redCount, greenCount: Integer;
  45.             end;
  46.  
  47.     var
  48.         gGameState: GameState;
  49.  
  50. (*Which tile is marked?*)
  51.         gMarkedTile: Point;
  52.  
  53. (* A boolean telling if we should quit yet or not *)
  54.         gDone: Boolean;
  55.  
  56. (* Pictures*)
  57.         emptyTile: PicHandle;
  58.         redTile: PicHandle;
  59.         greenTile: PicHandle;
  60.         redMarkedTile: PicHandle;
  61.         greenMarkedTile: PicHandle;
  62.  
  63.  
  64. (* A function that converts two coordinates to a value that can be used as index in the tile array*)
  65.  
  66.     function Point2Index (h: Integer; v: Integer): Integer;
  67.     begin
  68.         Point2Index := h + v * kArraySizeH;
  69.         Exit(Point2Index);
  70.     end; (*Point2Index*)
  71.  
  72.  
  73. (* Draw a tile *)
  74.  
  75.     procedure DrawTile (h: Integer; v: Integer);
  76.  
  77.         var
  78.             tileRectangle: Rect;
  79.  
  80.     begin
  81.         SetRect(tileRectangle, h * kTileSizeH, v * kTileSizeV, (h + 1) * kTileSizeH, (v + 1) * kTileSizeV);
  82.         case gGameState.tiles[Point2Index(h, v)] of
  83.             empty: 
  84.                 DrawPicture(emptyTile, tileRectangle);
  85.             red: 
  86.                 if (h = gMarkedTile.h) and (v = gMarkedTile.v) then
  87.                     DrawPicture(redMarkedTile, tileRectangle)
  88.                 else
  89.                     DrawPicture(redTile, tileRectangle);
  90.             green: 
  91.                 if (h = gMarkedTile.h) and (v = gMarkedTile.v) then
  92.                     DrawPicture(greenMarkedTile, tileRectangle)
  93.                 else
  94.                     DrawPicture(greenTile, tileRectangle);
  95.             otherwise
  96.                 PaintRect(tileRectangle);
  97.         end;
  98.     end; (*DrawTile*)
  99.  
  100.  
  101.     function IsLine (state: GameState; h: Integer; v: Integer; dh: Integer; dv: Integer): Boolean;
  102.         var
  103.             i: Integer;
  104.         procedure Return (val: Boolean);
  105.         begin
  106.             IsLine := val;
  107.             exit(IsLine);
  108.         end;
  109.     begin
  110.         for i := 0 to 1 do
  111.             begin
  112.                 if state.tiles[Point2Index(h + i * dh, v + i * dv)] = empty then
  113.                     Return(false);
  114.                 if state.tiles[Point2Index(h + i * dh, v + i * dv)] <> state.tiles[Point2Index(h + (i + 1) * dh, v + (i + 1) * dv)] then
  115.                     Return(false);
  116.             end;
  117.         IsLine := true;
  118.     end; (*IsLine*)
  119.  
  120.  
  121.     function Analyze (state: GameState): Boolean;
  122.         procedure Return (val: Boolean);
  123.         begin
  124.             Analyze := val;
  125.             exit(Analyze);
  126.         end;
  127.     begin
  128. { The analysis here is just to check if there is a victory}
  129. { There are eight combinations:}
  130.         if IsLine(state, 0, 0, 1, 1) then { diagonal from top-left}
  131.             Return(true);
  132.         if (IsLine(state, 2, 0, -1, 1)) then { diagonal from top-right}
  133.             Return(true);
  134.         if (IsLine(state, 0, 0, 1, 0)) then { first row}
  135.             Return(true);
  136.         if (IsLine(state, 0, 1, 1, 0)) then { second row}
  137.             Return(true);
  138.         if (IsLine(state, 0, 2, 1, 0)) then { third row}
  139.             Return(true);
  140.         if (IsLine(state, 0, 0, 0, 1)) then { first column}
  141.             Return(true);
  142.         if (IsLine(state, 1, 0, 0, 1)) then { second column}
  143.             Return(true);
  144.         if (IsLine(state, 2, 0, 0, 1)) then { third column}
  145.             Return(true);
  146.         Return(false);
  147.     end; (*Analyze*)
  148.  
  149.  
  150. (* Initialize - create window, load graphics *)
  151.  
  152.     procedure InitTicTacToe;
  153.         var
  154.             windowRectangle: Rect;
  155.             i: Integer;
  156. (*Set up the window*)
  157.     begin
  158.         SetRect(windowRectangle, 50, 50, 50 + kArraySizeH * kTileSizeH, 50 + kArraySizeV * kTileSizeV);
  159.         myWindow := NewCWindow(nil, windowRectangle, 'Tic-Tac-Toe', true, 0, WindowPtr(-1), false, 0);
  160.         SetPort(myWindow);
  161.  
  162. {$IFC UNDEFINED THINK_PASCAL}
  163.         qd.randSeed := TickCount;    (*Seed the random number generator*)
  164. {$ELSEC}
  165.         randSeed := TickCount;    (*Seed the random number generator*)
  166. {$ENDC}
  167.  
  168.         emptyTile := GetPicture(131);
  169.         redTile := GetPicture(132);
  170.         greenTile := GetPicture(133);
  171.         redMarkedTile := GetPicture(134);
  172.         greenMarkedTile := GetPicture(135);
  173.  
  174.         for i := 0 to kNumTiles - 1 do
  175.             gGameState.tiles[i] := empty;
  176.         gGameState.redCount := 0;
  177.         gGameState.greenCount := 0;
  178.         SetPt(gMarkedTile, -1, -1);
  179.     end; (*InitTicTacToe*)
  180.  
  181.  
  182. (* ValidMove checks if a tile clickedTile is inside the array bounds *and* near the player *)
  183.  
  184.     function ValidMove (clickedTile: Point): Boolean;
  185.     begin
  186. (* Valid tile?*)
  187.         if clickedTile.h >= 0 then
  188.             if clickedTile.v >= 0 then
  189.                 if clickedTile.h < kArraySizeH then
  190.                     if clickedTile.v < kArraySizeV then
  191.                         ValidMove := true
  192.                     else
  193.                         ValidMove := false;
  194.     end; (*ValidMove*)
  195.  
  196.  
  197. { FindComputerMove and FindPlayerMove are the two routines that perform the search.}
  198. {They call each other, recursively, up to the search depth.}
  199.  
  200. { return 1 if win}
  201. { return 0 if undecided}
  202. { return -1 if lose}
  203.  
  204.     const
  205.         kWinningMove = 1;
  206.         kNoWinMove = 0;
  207.         kLosingMove = -1;
  208.         kNoMove = -2;
  209.  
  210. {prototype FindPlayerMove}
  211.     function FindPlayerMove (state: GameState; var returnState: GameState; depth: Integer): Integer;
  212.     forward;
  213.  
  214.     function FindComputerMove (state: GameState; var returnState: GameState; depth: Integer): Integer;
  215.         var
  216.             tempState, junkState: GameState;
  217.             moveTo, moveFrom: Integer;
  218.             bestStateValue: Integer;
  219.             stateValue: Integer;
  220.  
  221.     begin
  222.  
  223.         bestStateValue := kNoMove;
  224.         returnState := state; { if all else fails}
  225.  
  226.         if state.redCount < 3 then
  227.             begin
  228.                 for moveTo := 0 to kNumTiles - 1 do
  229.                     begin
  230.                         if state.tiles[moveTo] = empty then
  231.                             begin
  232.                                 tempState := state;
  233.                                 tempState.tiles[moveTo] := red;
  234.                                 tempState.redCount := tempState.redCount + 1;
  235.                                 if Analyze(tempState) then
  236.                                     begin
  237.                                         returnState := tempState;
  238.                                         FindComputerMove := kWinningMove;
  239.                                         exit(FindComputerMove);
  240.                                     end;
  241.                                 if depth < kMaxDepth then
  242.                                     stateValue := -FindPlayerMove(tempState, junkState, depth + 1)
  243.                                 else
  244.                                     stateValue := kNoWinMove; { When we can't seach futher, set to undecided}
  245.                                 if (stateValue > bestStateValue) then
  246.                                     begin
  247.                                         returnState := tempState;
  248.                                         bestStateValue := stateValue;
  249.                                     end
  250.                                 else if (stateValue = bestStateValue) then
  251.                                     if Random > 0 then
  252.                                         returnState := tempState;
  253.                             end; {if empty}
  254.                     end; {for}
  255.             end {if}
  256.         else
  257.             begin
  258.                 for moveTo := 0 to kNumTiles - 1 do
  259.                     for moveFrom := 0 to kNumTiles - 1 do
  260.                         begin
  261.                             if state.tiles[moveTo] = empty then
  262.                                 if state.tiles[moveFrom] = red then
  263.                                     begin
  264.                                         tempState := state;
  265.                                         tempState.tiles[moveTo] := red;
  266.                                         tempState.tiles[moveFrom] := empty;
  267.                                         if Analyze(tempState) then
  268.                                             begin
  269.                                                 returnState := tempState;
  270.                                                 FindComputerMove := kWinningMove;
  271.                                                 exit(FindComputerMove);
  272.                                             end;
  273.                                         if depth < kMaxDepth then
  274.                                             stateValue := -FindPlayerMove(tempState, junkState, depth + 1)
  275.                                         else
  276.                                             stateValue := kNoWinMove;
  277.                                         if (stateValue > bestStateValue) then
  278.                                             begin
  279.                                                 returnState := tempState;
  280.                                                 bestStateValue := stateValue;
  281.                                             end
  282.                                         else if (stateValue = bestStateValue) then
  283.                                             if Random > 0 then
  284.                                                 returnState := tempState;
  285.                                     end; {if empty}
  286.                         end; {for}
  287.  
  288.             end;{if else}
  289.  
  290.         FindComputerMove := bestStateValue;
  291.         Exit(FindComputerMove);
  292.     end; (*FindComputerMove*)
  293.  
  294.  
  295.  
  296. {Same routine but for finding the best player move}
  297.     function FindPlayerMove (state: GameState; var returnState: GameState; depth: Integer): Integer;
  298.         var
  299.             tempState, junkState: GameState;
  300.             moveTo, moveFrom: Integer;
  301.             bestStateValue: Integer;
  302.             stateValue: Integer;
  303.  
  304.     begin
  305.  
  306.         bestStateValue := kNoMove;
  307.         returnState := state; { if all else fails}
  308.  
  309.         if state.greenCount < 3 then
  310.             begin
  311.                 for moveTo := 0 to kNumTiles - 1 do
  312.                     begin
  313.                         if state.tiles[moveTo] = empty then
  314.                             begin
  315.                                 tempState := state;
  316.                                 tempState.tiles[moveTo] := green;
  317.                                 tempState.redCount := tempState.greenCount + 1;
  318.                                 if Analyze(tempState) then
  319.                                     begin
  320.                                         returnState := tempState;
  321.                                         FindPlayerMove := kWinningMove;
  322.                                         exit(FindPlayerMove);
  323.                                     end;
  324.                                 if depth < kMaxDepth then
  325.                                     stateValue := -FindPlayerMove(tempState, junkState, depth + 1)
  326.                                 else
  327.                                     stateValue := kNoWinMove; { When we can't seach futher, set to undecided}
  328.                                 if (stateValue > bestStateValue) then
  329.                                     begin
  330.                                         returnState := tempState;
  331.                                         bestStateValue := stateValue;
  332.                                     end
  333.                                 else if (stateValue = bestStateValue) then
  334.                                     if Random > 0 then
  335.                                         returnState := tempState;
  336.                             end; {if empty}
  337.                     end; {for}
  338.             end {if}
  339.         else
  340.             begin
  341.                 for moveTo := 0 to kNumTiles - 1 do
  342.                     for moveFrom := 0 to kNumTiles - 1 do
  343.                         begin
  344.                             if state.tiles[moveTo] = empty then
  345.                                 if state.tiles[moveFrom] = green then
  346.                                     begin
  347.                                         tempState := state;
  348.                                         tempState.tiles[moveTo] := green;
  349.                                         tempState.tiles[moveFrom] := empty;
  350.                                         if Analyze(tempState) then
  351.                                             begin
  352.                                                 returnState := tempState;
  353.                                                 FindPlayerMove := kWinningMove;
  354.                                                 exit(FindPlayerMove);
  355.                                             end;
  356.                                         if depth < kMaxDepth then
  357.                                             stateValue := -FindComputerMove(tempState, junkState, depth + 1)
  358.                                         else
  359.                                             stateValue := kNoWinMove;
  360.                                         if (stateValue > bestStateValue) then
  361.                                             begin
  362.                                                 returnState := tempState;
  363.                                                 bestStateValue := stateValue;
  364.                                             end
  365.                                         else if (stateValue = bestStateValue) then
  366.                                             if Random > 0 then
  367.                                                 returnState := tempState;
  368.                                     end; {if empty}
  369.                         end; {for}
  370.  
  371.             end;{if else}
  372.  
  373.         FindPlayerMove := bestStateValue;
  374.         Exit(FindPlayerMove);
  375.     end; (*FindPlayerMove*)
  376.  
  377.  
  378.  
  379.  
  380.  
  381. (* Try to move the player to the position where we clicked. *)
  382.  
  383.     procedure PlayerMove (clickedTile: Point);
  384.  
  385.         var
  386.             h, v: Integer;
  387.             tileIndex: Integer;
  388.             oldMarkedTile: Point;
  389.             newState: GameState;
  390.             computerMoveResult: Integer;
  391.             err: OSErr;
  392.  
  393.     begin
  394.         tileIndex := Point2Index(clickedTile.h, clickedTile.v);
  395.  
  396. (* Valid move?*)
  397.         if (ValidMove(clickedTile)) then
  398. (* Yes! What is there? *)
  399.             begin
  400.                 if gGameState.greenCount < 3 then
  401.  
  402. (* All pieces are not placed yet. Click must be in an empty space! *)
  403.                     begin
  404.                         if gGameState.tiles[tileIndex] <> empty then
  405.                             begin
  406.                                 SysBeep(1);
  407.                                 exit(PlayerMove);
  408.                             end;
  409.                         gGameState.tiles[tileIndex] := green;
  410.                         gGameState.greenCount := gGameState.greenCount + 1;
  411.                         DrawTile(clickedTile.h, clickedTile.v);
  412.                         if (Analyze(gGameState)) then
  413.                 { Victory! Play a sound and set gDone!}
  414.                             begin
  415.                                 err := SndPlay(nil, SndListHandle(GetNamedResource('snd ', 'OK, you won')), false);
  416.                                 gDone := true;
  417.                                 exit(PlayerMove);
  418.                             end;
  419.  
  420.                     end
  421.                 else
  422. (* All pieces are placed. *)
  423.                     begin
  424.                         if gGameState.tiles[tileIndex] = green then { Mark a tile for later movement}
  425.                             begin
  426.                                 oldMarkedTile := gMarkedTile;
  427.                                 gMarkedTile.h := -1;                            { Make gMarkedTile invalid!}
  428.                                 DrawTile(oldMarkedTile.h, oldMarkedTile.v);    { Redraw old marked tile, if any}
  429.                                 gMarkedTile := clickedTile;                    { Set new gMarkedTile}
  430.                                 DrawTile(gMarkedTile.h, gMarkedTile.v);        { Draw new marked tile}
  431.                                 exit(PlayerMove);
  432.                             end
  433.                         else { Move a tile that was marked before}
  434.                             if (gGameState.tiles[tileIndex] = empty) then
  435.                                 begin
  436.                                     if ValidMove(gMarkedTile) then
  437.                                         begin
  438.                                             gGameState.tiles[Point2Index(gMarkedTile.h, gMarkedTile.v)] := empty;
  439.                                             gGameState.tiles[tileIndex] := green;
  440.                                             oldMarkedTile := gMarkedTile;
  441.                                             gMarkedTile.h := -1;                        { Make gMarkedTile invalid!}
  442.                                             DrawTile(oldMarkedTile.h, oldMarkedTile.v);    { Redraw old marked tile, if any}
  443.                                             DrawTile(clickedTile.h, clickedTile.v);        { Draw moved tile                    }
  444.  
  445.                                             if Analyze(gGameState) then
  446.                         { Victory! Play a sound and set gDone!}
  447.                                                 begin
  448.                                                     err := SndPlay(nil, SndListHandle(GetNamedResource('snd ', 'OK, you won')), false);
  449.                                                     gDone := true;
  450.                                                     Exit(PlayerMove);
  451.                                                 end;
  452.                                         end    { end valid gMarkedTile}
  453.                                     else
  454.                                         Exit(PlayerMove);
  455.                                 end    { end click in empty space}
  456.                             else (* Click in red tile. Beep and return. *)
  457.                                 begin
  458.                                     SysBeep(1);
  459.                                     exit(PlayerMove);
  460.                                 end;
  461.                     end; { end if 3 green else}
  462.  
  463.         { If we get here, the player has made a valid move!}
  464.                 computerMoveResult := FindComputerMove(gGameState, newState, 0);
  465.                 gGameState := newState;
  466.                 gMarkedTile.h := -1;
  467.         (*Draw all tiles!*)
  468.                 for h := 0 to kArraySizeH - 1 do
  469.                     for v := 0 to kArraySizeV - 1 do
  470.                         DrawTile(h, v);
  471.                 if Analyze(gGameState) then
  472.             { Computer victory! Play a sound and set gDone!}
  473.                     begin
  474.                         err := SndPlay(nil, SndListHandle(GetNamedResource('snd ', 'Haha, I won')), false);
  475.                         gDone := true;
  476.                         exit(PlayerMove);
  477.                     end;
  478.  
  479.             end; { end ValidMove}
  480.  
  481.     end; (*PlayerMove*)
  482.  
  483.  
  484. (* Standard inits *)
  485.  
  486.     procedure InitToolbox;
  487.     begin
  488. {$IFC UNDEFINED THINK_PASCAL}
  489.         InitGraf(@qd.thePort);
  490.         InitFonts;
  491.         FlushEvents(everyEvent, 0);
  492.         InitWindows;
  493.         InitMenus;
  494.         TEInit;
  495.         InitDialogs(nil);
  496. {$ENDC}
  497.         InitCursor;
  498.     end; (*InitToolbox*)
  499.  
  500.  
  501. (* Main program *)
  502.  
  503.     var
  504.         clickPoint, clickedTile: Point;
  505.         h, v: Integer;
  506.  
  507. begin
  508.     InitToolbox;
  509.     InitTicTacToe;
  510.  
  511. (*Draw all tiles!*)
  512.     for h := 0 to kArraySizeH - 1 do
  513.         for v := 0 to kArraySizeV - 1 do
  514.             DrawTile(h, v);
  515.  
  516. (*Initializations done! Run the game loop until the game ends.*)
  517.     repeat
  518.         if Button then
  519.             begin
  520.                 GetMouse(clickPoint);        (* Get the position of the click *)
  521.                 clickedTile.h := clickPoint.h div kTileSizeH;    (* Convert to grid. *)
  522.                 clickedTile.v := clickPoint.v div kTileSizeV;
  523.                 PlayerMove(clickedTile);    (* Try to move there *)
  524.                 while Button do
  525.                     ;        (* Wait until the mouse click ends *)
  526.             end;
  527.     until gDone;
  528.  
  529.     FlushEvents(mDownMask, 0);        (* Get rid of mouse down events! *)
  530. end. (*main program*)
  531.  
  532.  
  533. (* What's left for making a real game of it?*)
  534. (* Event processing, menus, "new game". That's about it. *)